Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit bec11291697bbcfc3c1eec4bbdd069ea5dba6378


Parents : eaf8d72
Author : Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-04-23T17:50:09-05:00

chore(release): bump version to 4.5.1 and update changelog

Changes
Diff

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ff676773..de145037 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,27 @@
All notable changes to this project will be documented in this file.
-## [4.5.0] - 2026-04-24
+## [4.5.1] - 2026-04-24
+
+### Identity switching
+
+- **Hotswap API**: Identity switch responses now include **display name** and **identity hash** so the client can refresh state without guessing from partial payloads.
+- **Event handling**: Identity switch events are **deduplicated** in **`App.vue`** and **`IdentitiesPage.vue`** so rapid or repeated signals do not stack duplicate work.
+- **Locales**: Confirmation copy for switching identities no longer tells users to restart when that is not required (aligned strings across supported languages).
+- **Tests**: New **HTTP** coverage for the identity switch API (success paths, validation, and error responses).
+
+### Desktop (Electron)
+
+- **Boot experience**: **Splash screen** on startup and **Codec2** loader scripts use **retry** logic so transient load failures are less likely to strand voice features.
+- **Developer tools**: **F12** toggles **DevTools**; the menu bar **auto-hides** in main windows for a cleaner ui.
+- **Content Security Policy**: CSP now allows **`wasm-unsafe-eval`** where needed so **WebAssembly** used by the stack (for example audio codec paths) can run under the hardened policy.
+
+### Microphone and translations
+
+- **Recording**: Microphone capture updates **error handling** and adjusts **AudioWorklet** import so failures surface more reliably instead of failing silently.
+- **Locales**: New and updated strings for **microphone** errors so users get clearer guidance when capture or permissions go wrong.
+
+## [4.5.0] - 2026-04-23
### TL;DR

diff --git a/README.md b/README.md
index 9d24ba9f..d6ace5f6 100644
--- a/README.md
+++ b/README.md
@@ -300,7 +300,7 @@ task build:all
## Versioning
-Current version in this repo is `4.5.0`.
+Current version in this repo is `4.5.1`.
- `package.json` is the JavaScript/Electron version source.
- `meshchatx/src/version.py` is synced from `package.json` using:

diff --git a/docs/meshchatx_on_raspberry_pi.md b/docs/meshchatx_on_raspberry_pi.md
index 94807d7f..730dc337 100644
--- a/docs/meshchatx_on_raspberry_pi.md
+++ b/docs/meshchatx_on_raspberry_pi.md
@@ -61,17 +61,17 @@ source ~/.profile
## 3) Install MeshChatX with pipx (recommended)
-Preferred option (recommended): install from a release wheel (4.5.0 or newer),
+Preferred option (recommended): install from a release wheel (4.5.1 or newer),
because the wheel bundles frontend assets.
```bash
pipx install /path/to/reticulum_meshchatx-<version>-py3-none-any.whl
```
-Direct example (v4.5.0):
+Direct example (v4.5.1):
```bash
-pipx install "https://git.quad4.io/RNS-Things/MeshChatX/releases/download/v4.4.0/reticulum_meshchatx-4.5.0-py3-none-any.whl"
+pipx install "https://git.quad4.io/RNS-Things/MeshChatX/releases/download/v4.5.1/reticulum_meshchatx-4.5.1-py3-none-any.whl"
```
`py3-none-any` wheels are architecture-independent, so the same wheel artifact

diff --git a/lang/README.de.md b/lang/README.de.md
index 0a5afcd9..d785b81b 100644
--- a/lang/README.de.md
+++ b/lang/README.de.md
@@ -295,7 +295,7 @@ task build:all
## Versionierung
-Aktuelle Version in diesem Repository: `4.5.0`.
+Aktuelle Version in diesem Repository: `4.5.1`.
- `package.json` ist die Quelle fuer die JavaScript/Electron-Version.
- `meshchatx/src/version.py` wird aus `package.json` synchronisiert mit:

diff --git a/lang/README.it.md b/lang/README.it.md
index 02ff3851..ae79f941 100644
--- a/lang/README.it.md
+++ b/lang/README.it.md
@@ -295,7 +295,7 @@ Scorciatoie `Makefile`:
## Versioning
-Versione attuale nel repository: `4.5.0`.
+Versione attuale nel repository: `4.5.1`.
- La fonte della versione JavaScript/Electron e `package.json`.
- `meshchatx/src/version.py` e sincronizzato da `package.json` con:

diff --git a/lang/README.ja.md b/lang/README.ja.md
index 07ca3a72..3ab2ceb7 100644
--- a/lang/README.ja.md
+++ b/lang/README.ja.md
@@ -295,7 +295,7 @@ task build:all
## バージョン管理
-このリポジトリの現在のバージョンは `4.5.0` です。
+このリポジトリの現在のバージョンは `4.5.1` です。
- JavaScript / Electron のバージョンソースは `package.json`。
- `meshchatx/src/version.py` は次で `package.json` と同期します:

diff --git a/lang/README.ru.md b/lang/README.ru.md
index 9b9bf0f4..6e17743f 100644
--- a/lang/README.ru.md
+++ b/lang/README.ru.md
@@ -295,7 +295,7 @@ task build:all
## Версионирование
-Текущая версия в репозитории: `4.5.0`.
+Текущая версия в репозитории: `4.5.1`.
- Источник версии JS/Electron — `package.json`.
- `meshchatx/src/version.py` синхронизируется из `package.json`:

diff --git a/lang/README.zh.md b/lang/README.zh.md
index c30145d0..7ab6f2ea 100644
--- a/lang/README.zh.md
+++ b/lang/README.zh.md
@@ -295,7 +295,7 @@ task build:all
## 版本
-本仓库当前版本: `4.5.0`。
+本仓库当前版本: `4.5.1`。
- JavaScript/Electron 版本以 `package.json` 为准。
- `meshchatx/src/version.py` 通过以下命令与 `package.json` 同步:

diff --git a/meshchatx/__init__.py b/meshchatx/__init__.py
index d3a31346..ecb92966 100644
--- a/meshchatx/__init__.py
+++ b/meshchatx/__init__.py
@@ -2,4 +2,4 @@
"""Reticulum MeshChatX - A mesh network communications app."""
-__version__ = "4.5.0"
+__version__ = "4.5.1"

diff --git a/meshchatx/meshchat.py b/meshchatx/meshchat.py
index fef4220e..33d4aed1 100644
--- a/meshchatx/meshchat.py
+++ b/meshchatx/meshchat.py
@@ -11593,9 +11593,10 @@ class ReticulumMeshChat:
if path.startswith("/reticulum-docs/") or path.startswith(
"/rnode-flasher/"
):
- script_sources = ["'self'", "'unsafe-inline'"]
+ script_sources = ["'self'", "'unsafe-inline'", "'wasm-unsafe-eval'"]
else:
- script_sources = ["'self'"]
+ # wasm-unsafe-eval: Codec2 / sox Emscripten WASM (sox.js) without full unsafe-eval
+ script_sources = ["'self'", "'wasm-unsafe-eval'"]
style_sources = ["'self'", "'unsafe-inline'"]
if self.current_context and self.current_context.config:

diff --git a/meshchatx/src/backend/data/THIRD_PARTY_NOTICES.txt b/meshchatx/src/backend/data/THIRD_PARTY_NOTICES.txt
index fc314fad..eb34c4e5 100644
--- a/meshchatx/src/backend/data/THIRD_PARTY_NOTICES.txt
+++ b/meshchatx/src/backend/data/THIRD_PARTY_NOTICES.txt
@@ -76,7 +76,7 @@ pycparser 3.0
pyserial 3.5
License: BSD
Author: Chris Liechti
-reticulum-meshchatx 4.5.0
+reticulum-meshchatx 4.5.1
License: 0BSD AND MIT
Author: Quad4
rns 1.1.9

diff --git a/meshchatx/src/version.py b/meshchatx/src/version.py
index 460b7af1..a18bd843 100644
--- a/meshchatx/src/version.py
+++ b/meshchatx/src/version.py
@@ -3,4 +3,4 @@
Do not edit by hand. Run: pnpm run version:sync
"""
-__version__ = "4.5.0"
+__version__ = "4.5.1"

diff --git a/package.json b/package.json
index 439d9144..93e7590c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "reticulum-meshchatx",
- "version": "4.5.0",
+ "version": "4.5.1",
"description": "A simple mesh network communications app powered by the Reticulum Network Stack",
"homepage": "https://git.quad4.io/RNS-Things/MeshChatX",
"desktopName": "reticulum-meshchatx.desktop",

diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD
index 9555bd92..ccdf36be 100644
--- a/packaging/arch/PKGBUILD
+++ b/packaging/arch/PKGBUILD
@@ -1,7 +1,7 @@
# Maintainer: Ivan <ivan@quad4.io>
pkgname=reticulum-meshchatx-git
_pkgname=reticulum-meshchatx
-pkgver=4.5.0.r0.gebacc00
+pkgver=4.5.1.r0.gebacc00
pkgrel=1
pkgdesc="A simple mesh network communications app powered by the Reticulum Network Stack"
arch=('x86_64' 'aarch64')
@@ -19,7 +19,7 @@ sha256sums=('SKIP'
pkgver() {
cd "$_pkgname"
git describe --long --tags 2>/dev/null | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || \
- printf "4.5.0.r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
+ printf "4.5.1.r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}
prepare() {

diff --git a/poetry.lock b/poetry.lock
index fdde8166..0980c2ba 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -470,15 +470,15 @@ pycparser = {version = "*", markers = "implementation_name != \"PyPy\""}
[[package]]
name = "click"
-version = "8.3.2"
+version = "8.3.3"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.10"
groups = ["dev"]
markers = "python_version < \"4.0\""
files = [
- {file = "click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d"},
- {file = "click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5"},
+ {file = "click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613"},
+ {file = "click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2"},
]
[package.dependencies]
@@ -1022,18 +1022,18 @@ zoneinfo = ["tzdata (>=2026.1) ; sys_platform == \"win32\" or sys_platform == \"
[[package]]
name = "idna"
-version = "3.11"
+version = "3.13"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
- {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"},
- {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"},
+ {file = "idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3"},
+ {file = "idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242"},
]
[package.extras]
-all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
+all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
name = "iniconfig"
@@ -2142,32 +2142,25 @@ testing = ["filelock"]
[[package]]
name = "python-msilib"
-version = "0.5.0"
+version = "0.6.0"
description = "Read and write Microsoft Installer files"
optional = false
python-versions = ">=3.13"
groups = ["dev"]
markers = "sys_platform == \"win32\" and python_version >= \"3.13\""
files = [
- {file = "python_msilib-0.5.0-cp313-cp313-win32.whl", hash = "sha256:240c9cebc402361e6fe32fb94020ce5a2674343122cb4dbd036fa59068d153c4"},
- {file = "python_msilib-0.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:cb1db6fa79cfff539a0f313c80f27760b3c452b8fb59878c4b1ce2959711541e"},
- {file = "python_msilib-0.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:3904771a9e31276da9d2de4e431e09b122a354b84b41746c6feaa14b91d2fd0d"},
- {file = "python_msilib-0.5.0-cp313-cp313t-win32.whl", hash = "sha256:67ab7fe107b6dbed42e609d0731ae9863c8aaf58e6015995e42671a9d215fe86"},
- {file = "python_msilib-0.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc3d895c1cb4f695344a7149909d460020d0e6f987d33ab40ea687d65dd1f2ad"},
- {file = "python_msilib-0.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:cc62a3d0500c1d6d21bea347c55215ae16c81bc749134c47e466f284a46b56fe"},
- {file = "python_msilib-0.5.0-cp314-cp314-win32.whl", hash = "sha256:e5023ab8da04a13769d1054e64415c02c198d442ce0f7cd9637204341d997379"},
- {file = "python_msilib-0.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:24cd942d097bafc97018698e1e66652143529eecaf8b8df55a624d580d81df7c"},
- {file = "python_msilib-0.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:f9da5045835d0de30b1087ff6fd6e0b6d076e248cd26405c0d399d8649ef9684"},
- {file = "python_msilib-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:821a164e72d77786d9e6d7cd28c060e6668665632a29f2c76abac85651f05f11"},
- {file = "python_msilib-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3fafe94d66f00bd3b26159b7ff43078421738288af5212744a1cb4a1cea52bc8"},
- {file = "python_msilib-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1c0b7a595dd457f7df5bd102c4c5abe5337ba0362aab2ece31bea29e6e7d9f2d"},
- {file = "python_msilib-0.5.0.tar.gz", hash = "sha256:5d9d0c2af2cafbc50d484cce4cfab1cb8cfd65d705d7111d216bc525a059113b"},
+ {file = "python_msilib-0.6.0-cp313-cp313-win32.whl", hash = "sha256:1145ce1e0eaf2af2948a02147415c621323cb2b2ff118fccdac333941547c11b"},
+ {file = "python_msilib-0.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:41b83e39c622a79042b3d1745421ce3492b954d701d7aeaf942d974c31291431"},
+ {file = "python_msilib-0.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:3ac47071f27755b11f9e01d7222c2babd84997410f4f0d96ded55c187b0b1fb8"},
+ {file = "python_msilib-0.6.0-cp314-cp314-win32.whl", hash = "sha256:8144e6249d12768e08844672175404b21a1f7ce4b7b6d539858a4f9ad6d6ca6c"},
+ {file = "python_msilib-0.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:281421203401afef589321f16d92d7aea8e5cf35f0345e47174346a3dde24808"},
+ {file = "python_msilib-0.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:c2fbc0d9388c937222f2ef717385aade5a3696bdc3b81150a0098c0cb79b58b9"},
+ {file = "python_msilib-0.6.0-cp314-cp314t-win32.whl", hash = "sha256:e511a1b4e247901416a39167090a5e58d1b65d1a5b401d0250dce983f4ab0980"},
+ {file = "python_msilib-0.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:848e7dd1fc61407d207fa4a2b7ac12b3a2de376d394a1d3f70ac4034bc9cd1a9"},
+ {file = "python_msilib-0.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a645358b02e17b0de61ea3f1dcd9f5d6b738148b1f317e2e864a08e04eeb80f3"},
+ {file = "python_msilib-0.6.0.tar.gz", hash = "sha256:47a6c16aa8528b797bd81e71cc544505b5444543319dbaf12011123b750c9d3c"},
]
-[package.extras]
-dev = ["bump-my-version (==1.2.4)", "cibuildwheel (==3.3.0)", "pre-commit (==4.5.0)"]
-tests = ["coverage (==7.12.0)", "pytest (==9.0.2)"]
-
[[package]]
name = "pyyaml"
version = "6.0.3"
@@ -2632,19 +2625,19 @@ files = [
[[package]]
name = "striprtf"
-version = "0.0.29"
+version = "0.0.31"
description = "A simple library to convert rtf to text"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "sys_platform == \"win32\""
files = [
- {file = "striprtf-0.0.29-py3-none-any.whl", hash = "sha256:0fc6a41999d015358d19627776b616424dd501ad698105c81d76734d1e14d91b"},
- {file = "striprtf-0.0.29.tar.gz", hash = "sha256:5a822d075e17417934ed3add6fc79b5fc8fb544fe4370b2f894cdd28f0ddd78e"},
+ {file = "striprtf-0.0.31-py3-none-any.whl", hash = "sha256:8ec8150a4213951675f711750a4a5b8a98a6da3ef85deeff0bb6e39cd2beeda4"},
+ {file = "striprtf-0.0.31.tar.gz", hash = "sha256:ce4b6403427d7d5cda95fdb61365432225f6fbe5ea6797e1357f4045d351ba2d"},
]
[package.extras]
-dev = ["build (>=1.0.0)", "pytest (>=7.0.0)"]
+dev = ["build (>=1.0.0)", "pytest (>=7.0.0)", "twine (>=6.1.0)"]
[[package]]
name = "textual"

diff --git a/pyproject.toml b/pyproject.toml
index f1b3bb62..970d9948 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "reticulum-meshchatx"
-version = "4.5.0"
+version = "4.5.1"
description = "A simple mesh network communications app powered by the Reticulum Network Stack"
authors = [
{name = "Quad4"}

diff --git a/tests/backend/test_app_status_tracking.py b/tests/backend/test_app_status_tracking.py
index ed4e858a..d4c6d800 100644
--- a/tests/backend/test_app_status_tracking.py
+++ b/tests/backend/test_app_status_tracking.py
@@ -96,8 +96,8 @@ async def test_app_status_endpoints(mock_rns_minimal, temp_dir):
app_instance.config.set("tutorial_seen", True)
assert app_instance.config.get("tutorial_seen") == "true"
- app_instance.config.set("changelog_seen_version", "4.5.0")
- assert app_instance.config.get("changelog_seen_version") == "4.5.0"
+ app_instance.config.set("changelog_seen_version", "4.5.1")
+ assert app_instance.config.get("changelog_seen_version") == "4.5.1"
# Test app_info returns these values
with ExitStack() as info_stack:
@@ -113,4 +113,4 @@ async def test_app_status_endpoints(mock_rns_minimal, temp_dir):
assert val == "true"
val = app_instance.config.get("changelog_seen_version")
- assert val == "4.5.0"
+ assert val == "4.5.1"

diff --git a/tests/frontend/AppModals.test.js b/tests/frontend/AppModals.test.js
index 072a9e50..b915a34b 100644
--- a/tests/frontend/AppModals.test.js
+++ b/tests/frontend/AppModals.test.js
@@ -62,9 +62,9 @@ describe("App.vue Modals", () => {
return Promise.resolve({
data: {
app_info: {
- version: "4.5.0",
+ version: "4.5.1",
tutorial_seen: true,
- changelog_seen_version: "4.5.0",
+ changelog_seen_version: "4.5.1",
},
},
});
@@ -94,7 +94,7 @@ describe("App.vue Modals", () => {
return Promise.resolve({
data: {
app_info: {
- version: "4.5.0",
+ version: "4.5.1",
tutorial_seen: false,
changelog_seen_version: "0.0.0",
},
@@ -153,7 +153,7 @@ describe("App.vue Modals", () => {
return Promise.resolve({
data: {
app_info: {
- version: "4.5.0",
+ version: "4.5.1",
tutorial_seen: true,
changelog_seen_version: "3.9.0",
},
@@ -161,7 +161,7 @@ describe("App.vue Modals", () => {
});
}
if (url === "/api/v1/app/changelog") {
- return Promise.resolve({ data: { html: "<h1>New Features</h1>", version: "4.5.0" } });
+ return Promise.resolve({ data: { html: "<h1>New Features</h1>", version: "4.5.1" } });
}
if (url === "/api/v1/config") return Promise.resolve({ data: { config: { theme: "dark" } } });
if (url === "/api/v1/auth/status") return Promise.resolve({ data: { auth_enabled: false } });

diff --git a/tests/frontend/ChangelogModal.test.js b/tests/frontend/ChangelogModal.test.js
index 65fd25cb..339dffa1 100644
--- a/tests/frontend/ChangelogModal.test.js
+++ b/tests/frontend/ChangelogModal.test.js
@@ -78,7 +78,7 @@ describe("ChangelogModal.vue", () => {
axiosMock.get.mockResolvedValue({
data: {
html: "<h1>Test</h1>",
- version: "4.5.0",
+ version: "4.5.1",
},
});
@@ -95,7 +95,7 @@ describe("ChangelogModal.vue", () => {
axiosMock.get.mockResolvedValue({
data: {
html: "<h1>Test</h1>",
- version: "4.5.0",
+ version: "4.5.1",
},
});
@@ -114,7 +114,7 @@ describe("ChangelogModal.vue", () => {
axiosMock.get.mockResolvedValue({
data: {
html: "<h1>Test</h1>",
- version: "4.5.0",
+ version: "4.5.1",
},
});


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────